﻿/* Copyright (c) 2011-2012, Zetatron Consulting
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Zetatron Consulting nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using AionCxxLibrary.Enums;
using System.Xml;
using AionCxxLibrary.Utilities;
using AionCxxLibrary.Models;
using System.Configuration;
using WorldServer.Models;

namespace WorldServer.Data.Templates
{
    [XmlRoot("client_item")]
    public class ItemTemplate : IPostDeserializeAwareTemplate
    {
        static string[] RejectedElements = new string[] { "mesh", "mesh_change", "material", "dmg_decal", "icon_name", "blade_fx", "trail_tex", "equip_bone", "attack_gap", "ui_sound_type",
        "combat_equip_bone", "item_fx", "combat_item_fx", "confirm_to_delete_cash_item", "ammo_bone", "ammo_fx", "ammo_speed", "trade_in_item_list", "default_color_m", "default_color_f",
        "visual_slot", "guild_level_permitted", "use_emblem", "quest", "tool_type", "motion_name", "category", "activation_mode", "use_fx", "use_fx_bone", "proc_fx", "doc_bg", "scale",
        "difficulty", "equip_type" };

        [XmlElement("id")]
        public int ID;

        [XmlElement("name")]
        public string Name;

        [XmlElement("desc")]
        public string Description;

        [XmlElement("desc_long")]
        public string DescriptionEx;

        [XmlElement("desc_craftman")]
        public string DescriptionCraftman;

        [XmlElement("weapon_type")]
        public WeaponType WeaponType = WeaponType.NoWeapon;
        [XmlElement("armor_type")]
        public ArmorType ArmorType = ArmorType.NoArmor;
        [XmlElement("attack_type")]
        public AttackType AttackType = AttackType.Physical;
        [XmlElement("attack_delay")]
        public short AttackDelay;
        [XmlElement("attack_range")]
        public float AttackRange;
        [XmlElement("hit_count")]
        public byte HitCount;
        [XmlElement("item_type")]
        public ItemType ItemType;
        [XmlElement("price")]
        public long Price;
        [XmlElement("abyss_point")]
        public long PriceAbyss;
        [XmlElement("max_stack_count")]
        public int MaxStackCount;
        [XmlElement("equipment_slots")]
        public EquipmentSlot EquipmentSlot = EquipmentSlot.None;
        [XmlElement("min_damage")]
        public short DamageMin;
        [XmlElement("max_damage")]
        public short DamageMax;
        [XmlElement("quality")]
        public ItemQuality Quality;
        [XmlElement("level")]
        public byte Level;
        [XmlElement("basic_length")]
        public float BasicLength;
        [XmlElement("temporary_exchange_time")]
        public byte TemporaryExchangeTime;
        [XmlElement("cash_available_minute")]
        public int CashAvailableMinute;

        [XmlElement("burn_on_attack")]
        public short BurnOnAttack;
        [XmlElement("burn_on_defend")]
        public short BurnOnDefend;
        [XmlElement("charge_level")]
        public byte ChargeLevel;
        [XmlElement("charge_price1")]
        public float ChargePrice1;
        [XmlElement("charge_price2")]
        public float ChargePrice2;

        [XmlElement("abyss_item")]
        public string AbyssItem = "";
        [XmlElement("abyss_item_count")]
        public int AbyssItemCount;

        [XmlElement("extra_currency_item")]
        public string ExtraCurrencyItem;
        [XmlElement("extra_currency_item_count")]
        public int ExtraCurrencyItemCount;

        [XmlElement("price_per_use")]
        public byte PricePerUse;
        [XmlElement("activation_count")]
        public short ActivationCount;
        [XmlElement("extract_skin_type")]
        public byte ExtractSkinType;
        [XmlElement("expire_time")]
        public int ExpireTime;

        [XmlElement("unit_sell_count")]
        public ushort UnitSellCount;

        [XmlElement("activate_target")]
        public string ActivateTarget;
        [XmlElement("activate_target_race")]
        public string ActivateTargetRace;
        [XmlElement("target_item_level_min")]
        public byte TargetItemLevelMin;
        [XmlElement("target_item_level_max")]
        public byte TargetItemLevelMax;

        [XmlElement("area_to_use")]
        public string AreaToUse;
        [XmlElement("ownership_world")]
        public string OwnershipWorld;

        /// <summary>
        /// Teleport Scrolls
        /// </summary>
        [XmlElement("return_alias")]
        public string ReturnAlias = "none";
        [XmlElement("return_worldid")]
        public string ReturnWorldID;

        [XmlElement("use_delay_type_id")]
        public int DelayID;
        [XmlElement("use_delay")]
        public int UseDelay;
        [XmlElement("casting_delay")]
        public int CastingDelay;

        [XmlElement("tag")]
        public string Tag;

        [XmlElement("bm_restrict_category")]
        public byte BM_RestrictCategory;

        // Permissions
        /// <summary>
        /// Permission defining if this item should be unique in storages.
        /// Default: false
        /// </summary>
        [XmlElement("lore")]
        public bool Lore = false;
        /// <summary>
        /// Permission defining whether this item can be exchanged with other characters.
        /// Default: false
        /// </summary>
        [XmlElement("can_exchange")]
        public bool CanExchange = false;
        /// <summary>
        /// Permission defining whether this item can be selled to NPCs.
        /// Default: false
        /// </summary>
        [XmlElement("can_sell_to_npc")]
        public bool CanSellToNpc = false;
        /// <summary>
        /// Permissing defining if this item is storable in the character's WH.
        /// Default: false
        /// </summary>
        [XmlElement("can_deposit_to_character_warehouse")]
        public bool CanStore_CharWH = false;
        /// <summary>
        /// Permission defining if this item is storable in the character's Account WH.
        /// Default: false
        /// </summary>
        [XmlElement("can_deposit_to_account_warehouse")]
        public bool CanStore_AccountWH = false;
        /// <summary>
        /// Permission defining if this item is storable in the character's Legion WH.
        /// Default: false
        /// </summary>
        [XmlElement("can_deposit_to_guild_warehouse")]
        public bool CanStore_GuildWH = false;
        /// <summary>
        /// Permission defining if this item can be extracted using Extraction Tools.
        /// Default: false
        /// </summary>
        [XmlElement("breakable")]
        public bool Breakable = false;
        /// <summary>
        /// Permission defining if this item has to be soul bound.
        /// Default: false
        /// </summary>
        [XmlElement("soul_bind")]
        public bool SoulBindRequired = false;
        /// <summary>
        /// Permission defining if this item should be removed from character's storages when logging out.
        /// Default: false
        /// </summary>
        [XmlElement("remove_when_logout")]
        public bool RemoveWhenLogout = false;
        /// <summary>
        /// Permission defining which gender is allowed to own this item.
        /// Default: All
        /// </summary>
        [XmlElement("gender_permitted")]
        public GenderPermitted GenderPermitted;
        /// <summary>
        /// Permission defining whether this item can be enchanted or not.
        /// Default: true
        /// </summary>
        [XmlElement("no_enchant")]
        public bool NoEnchant = true;
        /// <summary>
        /// Permission defining whether this item can proc an enchant.
        /// Default: false
        /// </summary>
        [XmlElement("can_proc_enchant")]
        public bool CanProcEnchant = false;
        /// <summary>
        /// Permission defining whether this item can be fusioned with another.
        /// Default: false
        /// </summary>
        [XmlElement("can_composite_weapon")]
        public bool CanCompositeWeapon = false;
        /// <summary>
        /// Permission defining whether this item can be splitted once fusioned.
        /// Default: false
        /// </summary>
        [XmlElement("can_split")]
        public bool CanSplit = false;
        /// <summary>
        /// Permission defining whether this item can be deleted from inventory.
        /// Default: false
        /// </summary>
        [XmlElement("item_drop_permitted")]
        public bool DropPermitted = true;
        /// <summary>
        /// Races allowed to use this item.
        /// Default: string("pc_light pc_dark") both races.
        /// </summary>
        [XmlElement("race_permitted")]
        public string RacePermitted = "pc_light pc_dark";
        /// <summary>
        /// Undefined.
        /// </summary>
        [XmlElement("cannot_extraction")]
        public bool CannotExtract = true;
        [XmlElement("cannot_matter_option")]
        public bool CannotMatterOption = true;
        [XmlElement("cannot_matter_enchant")]
        public bool CannotMatterEnchant = false;
        /// <summary>
        /// Permission defining whether this item can be dyed.
        /// Default: false
        /// </summary>
        [XmlElement("can_dye")]
        public bool CanDye = false;
        /// <summary>
        /// Whether this item is a bundle of other items.
        /// Default: 0 (not a bundle).
        /// </summary>
        [XmlElement("disassembly_item")]
        public byte DisassemblyItem;
        /// <summary>
        /// Combine Skill to use for this item.
        /// Default: null (not usable for transmutation)
        /// </summary>
        [XmlElement("combineskill")]
        public string CombineSkill;
        /// <summary>
        /// undefined.
        /// </summary>
        [XmlElement("breakdown")]
        public byte Breakdown;
        /// <summary>
        /// Whether this quest item should drop for all members of a group, or only one.
        /// </summary>
        [XmlElement("drop_each_member")]
        public bool DropEachMember;
        /// <summary>
        /// 0 = false, 1 = true
        /// </summary>
        [XmlElement("cannot_changeskin")]
        public byte CannotChangeskin;
        /// <summary>
        /// 0 = false, 1 = true
        /// </summary>
        [XmlElement("cash_item")]
        public byte CashItem;

        // Base Stats
        [XmlElement("str")]
        public short Strength;
        [XmlElement("agi")]
        public short Agility;
        [XmlElement("kno")]
        public short Knowledge;
        [XmlElement("hit_accuracy")]
        public short PhysicalAccuracy;
        [XmlElement("critical")]
        public short PhysicalCritical;
        [XmlElement("parry")]
        public short Parry;
        [XmlElement("magical_skill_boost")]
        public short MagicalSkillBoost;
        [XmlElement("magical_hit_accuracy")]
        public short MagicalAccuracy;
        [XmlElement("dodge")]
        public short Dodge;
        [XmlElement("magical_resist")]
        public short MagicalResist;
        [XmlElement("physical_defend")]
        public short PhysicalDefend;
        [XmlElement("block")]
        public short Block;
        [XmlElement("damage_reduce")]
        public short DamageReduce;
        [XmlElement("reduce_max")]
        public short ReduceMax;

        [XmlElement("gathering_point")]
        public short GatheringPoints;

        [XmlElement("stigma_type")]
        public StigmaType StigmaType;
        [XmlElement("require_shard")]
        public short StigmaRequiredShards;

        [XmlElement("option_slot_bonus")]
        public byte OptionSlotBonus;
        [XmlElement("bonus_apply")]
        public BonusApplyCondition BonusApplyCondition;

        [XmlElement("craft_recipe_info")]
        public string CraftRecipe;
        [XmlElement("sub_enchant_material_many")]
        public ushort SubEnchantMaterialMany;
        [XmlElement("sub_enchant_material_effect_type")]
        public SubEnchantMaterialEffectType SubEnchantMaterialEffectType;
        [XmlElement("sub_enchant_material_effect")]
        public ushort SubEnchantMaterialEffect;
        [XmlElement("package_permitted")]
        public byte PackagePermitted;

        [XmlElement("activation_skill")]
        public string ActivationSkill;
        [XmlElement("activation_level")]
        public byte ActivationSkillLevel;

        [XmlElement("doping_pet_useable")]
        public byte DopingPetUsable;

        [XmlElement("stat_enchant_type")]
        public string stat_enchant_type;
        [XmlIgnore]
        public Stat StatEnchantType = Stat.None;
        [XmlElement("stat_enchant_value")]
        public ushort StatEnchantValue;
        [XmlElement("proc_enchant_skill")]
        public string ProcEnchantSkill;
        [XmlElement("proc_enchant_skill_level")]
        public byte ProcEnchantSkillLevel;
        [XmlElement("proc_enchant_effect_occur_prob")]
        public ushort ProcEnchantEffectOccurProb;
        [XmlElement("proc_enchant_effect_occur_left_prob")]
        public ushort ProcEnchantEffectOccurLeftProb;
        [XmlElement("desc_proc")]
        public string ProcEnchantDescription;

        // Actions
        /// <summary>
        /// The conditioning level to be applied to the weapon on which this item is used.
        /// Default: 0 (Not a conditioning item)
        /// </summary>
        [XmlElement("charge_capacity")]
        public byte ChargeCapacity;
        /// <summary>
        /// The boost to apply to Physical and Magical attack for Power Shards.
        /// Default: 0 (No boost)
        /// </summary>
        [XmlElement("weapon_boost_value")]
        public byte WeaponBoostValue;
        /// <summary>
        /// The color to dye an item into using this dye.
        /// Default: null (Not a dying item)
        /// </summary>
        [XmlElement("dyeing_color")]
        public string DyeColor;
        /// <summary>
        /// The skill to teach the character when using this skill scroll.
        /// Default: null (Not a skill scroll)
        /// </summary>
        [XmlElement("skill_to_learn")]
        public string SkillToLearn;
        /// <summary>
        /// The emote to add to the character using this cash card.
        /// Default: 0 (Not an emote card)
        /// </summary>
        [XmlElement("cash_social")] // add emote
        public byte CashSocial;
        /// <summary>
        /// The title to add to the character using this card card.
        /// Default: 0 (Not a title card)
        /// </summary>
        [XmlElement("cash_title")]
        public byte CashTitle;
        /// <summary>
        /// The levels to add to character warehouse when using this WH extend card.
        /// Default: 0 (Not a WH card)
        /// </summary>
        [XmlElement("inven_warehouse_max_extendlevel")]
        public byte WarehouseExtendLevel;
        /// <summary>
        /// The cosmetic preset to apply to the character using this cosmetic card.
        /// Default: null (Not a cosmetic card)
        /// </summary>
        [XmlElement("cosmetic_name")]
        public string CosmeticName;
        /// <summary>
        /// Still undetermined.
        /// </summary>
        [XmlElement("coupon_item")]
        public string CouponItem;
        /// <summary>
        /// Still undetermined.
        /// </summary>
        [XmlElement("coupon_item_count")]
        public ushort CouponItemCount;
        /// <summary>
        /// The name of the housing object to create when using this item.
        /// Default: null (Not a housing item)
        /// </summary>
        [XmlElement("summon_housing_object")]
        public string SummonHousingObject;
        /// <summary>
        /// The name of kisk to spawn.
        /// Default: null (Not a kisk)
        /// </summary>
        [XmlElement("toy_pet_name")]
        public string KiskName;
        /// <summary>
        /// The name of toypet to create from this egg.
        /// Default: null (Not a toypet egg)
        /// </summary>
        [XmlElement("func_pet_name")]
        public string ToyPetName;
        /// <summary>
        /// Specifies a limited time for using this toypet.
        /// Default: 0 (no limit time)
        /// </summary>
        [XmlElement("func_pet_dur_minute")]
        public int ToyPetDuration;

        // Motion-related
        [XmlElement("custom_idle_anim")]
        public string CustomAnimation_Idle;
        [XmlElement("custom_run_anim")]
        public string CustomAnimation_Run;
        [XmlElement("custom_jump_anim")]
        public string CustomAnimation_Jump;
        [XmlElement("custom_rest_anim")]
        public string CustomAnimation_Rest;
        [XmlElement("anim_expire_time")]
        public int CustomAnimationExpireTime;

        /// <summary>
        /// The name of instance to reset cooldown of.
        /// Default: null (not an instance reset scroll)
        /// </summary>
        [XmlElement("init_coolt_instance")]
        public string InstanceCooltimeResetID;

        // 2.7 stuff
        public string f2p_pack_name;
        public ushort f2p_pack_available;
        public byte trial_user_can_vendor_buy;

        public ushort Mask;

        public int NameID;

        /// <summary>
        /// Item Stat Modifiers
        /// </summary>
        [XmlIgnore]
        public List<KeyValuePair<Stat, StatEntry>> Modifiers = new List<KeyValuePair<Stat, StatEntry>>();

        [XmlIgnore]
        Dictionary<CharacterClass, byte> CharLevelMin = new Dictionary<CharacterClass, byte>();
        [XmlIgnore]
        Dictionary<CharacterClass, byte> CharLevelMax = new Dictionary<CharacterClass, byte>();
        /// <summary>
        /// Those skills (name/minlevel) are required in order to enable this Stigma Stone.
        /// Default: Count == 0 which means no required skill.
        /// </summary>
        [XmlIgnore]
        public Dictionary<string, byte> StigmaRequiredSkills = new Dictionary<string, byte>();
        /// <summary>
        /// Those skills (name/level) will be added to character when socketing this Stigma Stone.
        /// Default: Count == 0 which means not a Stigma Stone.
        /// </summary>
        [XmlIgnore]
        public Dictionary<string, byte> StigmaGainedSkills = new Dictionary<string, byte>();

        /// <summary>
        /// List of unhandled XML Elements
        /// </summary>
        [XmlAnyElement]
        public List<XmlElement> OtherElements;

        protected override string[] GetRejectedElements()
        {
            return RejectedElements;
        }

        protected override List<XmlElement> GetOtherElements()
        {
            return OtherElements;
        }

        public int[] GetAcceptableCharLevels(CharacterClass clazz)
        {
            int[] result = new int[] { 1, 55 };
            if (CharLevelMin.ContainsKey(clazz))
                result[0] = CharLevelMin[clazz];
            if (CharLevelMax.ContainsKey(clazz))
                result[1] = CharLevelMax[clazz];
            return result;
        }

        public override void PostDeserialize()
        {
            base.PostDeserialize();

            // Normalize strings ids
            Name = Normalize(Name);
            Description = Normalize(Description);
            DescriptionEx = Normalize(DescriptionEx);
            DescriptionCraftman = Normalize(DescriptionCraftman);
            AbyssItem = Normalize(AbyssItem);
            ExtraCurrencyItem = Normalize(ExtraCurrencyItem);
            AreaToUse = Normalize(AreaToUse);
            OwnershipWorld = Normalize(OwnershipWorld);
            ReturnAlias = Normalize(ReturnAlias);
            ReturnWorldID = Normalize(ReturnWorldID);
            ActivationSkill = Normalize(ActivationSkill);
            CraftRecipe = Normalize(CraftRecipe);
            ProcEnchantSkill = Normalize(ProcEnchantSkill);
            ProcEnchantDescription = Normalize(ProcEnchantDescription);
            SkillToLearn = Normalize(SkillToLearn);
            CosmeticName = Normalize(CosmeticName);
            CouponItem = Normalize(CouponItem);
            SummonHousingObject = Normalize(SummonHousingObject);
            ToyPetName = Normalize(ToyPetName);
            KiskName = Normalize(KiskName);
            CustomAnimation_Idle = Normalize(CustomAnimation_Idle);
            CustomAnimation_Jump = Normalize(CustomAnimation_Jump);
            CustomAnimation_Rest = Normalize(CustomAnimation_Rest);
            CustomAnimation_Run = Normalize(CustomAnimation_Run);
            InstanceCooltimeResetID = Normalize(InstanceCooltimeResetID);

            // Generate Name ID
            if (Description != null)
            {
                NameID = Datastore.StringsByName[Description].ID;
            }

            // StatEnchantType
            if (stat_enchant_type != null)
            {
                StatEnchantType = Stats.ForAlias(stat_enchant_type);
                stat_enchant_type = "";
            }

            // Generate mask
            Mask = 0;
            if (Lore)
                Mask |= (int)ItemPermission.Lore;
            if (CanExchange)
                Mask |= (int)ItemPermission.CanExchange;
            if (CanSellToNpc)
                Mask |= (int)ItemPermission.CanSellToNpc;
            if (CanStore_CharWH)
                Mask |= (int)ItemPermission.CanStore_CharWH;
            if (CanStore_AccountWH)
                Mask |= (int)ItemPermission.CanStore_AccountWH;
            if (CanStore_GuildWH)
                Mask |= (int)ItemPermission.CanStore_LegionWH;
            if (Breakable)
                Mask |= (int)ItemPermission.Breakable;
            if (SoulBindRequired)
                Mask |= (int)ItemPermission.SoulBindRequired;
            if (RemoveWhenLogout)
                Mask |= (int)ItemPermission.RemoveWhenLogout;
            if (!NoEnchant)
                Mask |= (int)ItemPermission.CanEnchant;
            if (CanProcEnchant)
                Mask |= (int)ItemPermission.CanProcEnchant;
            if (CanCompositeWeapon)
                Mask |= (int)ItemPermission.CanCompositeWeapon;
            if (CannotChangeskin == 0)
                Mask |= (int)ItemPermission.CanChangeSkin;
            if (CanSplit)
                Mask |= (int)ItemPermission.CanSplitWeapon;
            if (DropPermitted)
                Mask |= (int)ItemPermission.Deletable;
            if (CanDye)
                Mask |= (int)ItemPermission.CanDye;

            // Process modifiers
            List<XmlElement> bonusAttributes = OtherElements.FindAll(el => el.Name.StartsWith("bonus_attr"));
            foreach (XmlElement bonus_attr in bonusAttributes)
            {
                string[] bonus_data = bonus_attr.InnerText.Replace("  ", " ").Split(' ');
                Stat stat = Stats.ForAlias(bonus_data[0]);

                bool percent = false;
                int value;
                if (bonus_data[1].Contains('%'))
                {
                    value = int.Parse(bonus_data[1].Replace("%", ""));
                    percent = true;
                }
                else
                    value = int.Parse(bonus_data[1]);

                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(stat, new StatEntry(value, percent)));

                OtherElements.Remove(bonus_attr);
            }
            if(Strength > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.Attack, new StatEntry(Strength, false)));
            if(Agility > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.Agility, new StatEntry(Agility, false)));
            if(Knowledge > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.Knowledge, new StatEntry(Knowledge, false)));
            if(PhysicalDefend > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.PhysicalDefend, new StatEntry(PhysicalDefend, false)));
            if(PhysicalAccuracy > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.PhysicalAccuracy, new StatEntry(PhysicalAccuracy, false)));
            if(PhysicalCritical > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.PhysicalCritical, new StatEntry(PhysicalCritical, false)));
            if(Parry > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.Parry, new StatEntry(Parry, false)));
            if(MagicalSkillBoost > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.BoostMagicalSkill, new StatEntry(MagicalSkillBoost, false)));
            if(MagicalAccuracy > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.MagicalAccuracy, new StatEntry(MagicalAccuracy, false)));
            if(Dodge > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.Dodge, new StatEntry(Dodge, false)));
            if(MagicalResist > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.MagicalResist, new StatEntry(MagicalResist, false)));
            if(Block > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.Block, new StatEntry(Block, false)));
            if(DamageReduce > 0)
                Modifiers.Add(new KeyValuePair<Stat,StatEntry>(Stat.DamageReduce, new StatEntry(DamageReduce, false)));

            // Process class requirements
            foreach (XmlElement class_req_attr in OtherElements.FindAll(el => CharacterClassEnum.ForAlias(el.Name.Replace("_max", "")) != CharacterClass.None))
            {
                bool max = class_req_attr.Name.Contains("_max");
                byte value = byte.Parse(class_req_attr.InnerText);
                CharacterClass clazz = CharacterClassEnum.ForAlias(class_req_attr.Name.Replace("_max", ""));
                if (max)
                    CharLevelMax.Add(clazz, value);
                else
                    CharLevelMin.Add(clazz, value);
                OtherElements.Remove(class_req_attr);
            }

            // Process Stigma Required Skills
            foreach (XmlElement stigma_req_sk in OtherElements.FindAll(el => el.Name.StartsWith("require_skill") && !el.Name.EndsWith("_lv")))
            {
                string[] skills = stigma_req_sk.InnerText.Split(',');
                XmlElement lvlelem = OtherElements.Find(el => el.Name == stigma_req_sk.Name + "_lv");
                if (lvlelem == null)
                {
                    Log.Warn("Stigma skill requirement without level, skipping !");
                    continue;
                }
                foreach(string sk in skills)
                {
                    StigmaRequiredSkills.Add(sk, byte.Parse(lvlelem.InnerText));
                }
                OtherElements.Remove(lvlelem);
                OtherElements.Remove(stigma_req_sk);
            }

            // Process Stigma Gained Skills
            foreach (XmlElement stigma_g_sk in OtherElements.FindAll(el => el.Name.StartsWith("gain_skill")))
            {
                XmlElement lvelem = OtherElements.Find(el => el.Name == stigma_g_sk.Name.Replace("skill", "level"));
                StigmaGainedSkills.Add(stigma_g_sk.Name, byte.Parse(lvelem.InnerText));
                OtherElements.Remove(lvelem);
                OtherElements.Remove(stigma_g_sk);
            }

            // Print out other elements for debug
            if (ConfigurationManager.AppSettings["DebugClientItems"].ToString() == "true")
            {
                foreach (XmlElement elem in OtherElements)
                {
                    Console.WriteLine("Unhandled Item Element in [" + ID + "] : '" + elem.Name + "' value = '" + elem.InnerText + "'");
                    Console.ReadLine();
                }
            }
        }

    }
    
    public class ItemIDCountPair
    {
        [XmlAttribute]
        public int ID;
        [XmlAttribute]
        public int Count;
    }

    public class ItemList
    {
        [XmlElement("Item")]
        public ItemIDCountPair[] Items;
    }
    
    [XmlRoot("character_initial_data")]
    public class CharacterInitialData
    {
        [XmlElement("Point")]
        public Vector5D[] BirthPoints;

        /// <summary>
        /// Holds the items to add to a newly created character.
        /// 4 entries, 0 = Warrior, 1 = Scout ...
        /// To calculate offset from class :
        /// 1. Make sure class is starting class : if(((int)Class) % 3) == 0)
        /// 2. Calculate int offset = ((int)Class / 3);
        /// </summary>
        [XmlElement("Items")]
        public ItemList[] ItemLists;
        
    }

    [XmlRoot("strings")]
    public class ClientStringTemplate : IPostDeserializeAwareTemplate
    {
        [XmlElement("id")]
        public int ID;
        [XmlElement("name")]
        public string Name;
        [XmlElement("body")]
        public string Body;
        
        protected override string[] GetRejectedElements()
        {
            return null;
        }

        protected override List<XmlElement> GetOtherElements()
        {
            return null;
        }

        public override void PostDeserialize()
        {
            base.PostDeserialize();
            Name = Normalize(Name);
        }

    }

}
